<?php

namespace Core\Data\SQL;

use \Core\StringHandling as StringHandling;


class MySQL implements Adapter {

    protected $resConnection;
    private $boolPersistent;
    private $strDatabase;

    /**
     * Holds the number of queries sent.
     *
     * @var integer
     */
    private $intNumQueriesSent = 0;

    /**
     * Holds the total number of queries sent across all instances of Connection.
     *
     * @var integer
     */
    private static $intTotalNumQueriesSent = 0;

    // Public read only members.

    /**
     * Holds information about an SQL server.
     *
     * @var Core\Data\SQL\ServerInformation
     */
    private $cstrURL;
    private $_Host;
    private $_Username;
    private $_Password;


    /**
     * Opens the connection.
     *
     * @param Core_Data_SQLServerInformation $SQLServerInformation Information about the server.
     * @param boolean $boolPersistent Option to make the connection a persistent connection, <kbd>false</kbd> by default.
     * @exception Cannot connect to the MySQL database server. <i>Exception code 1</i>.
     * @exception Cannot open the SQLite database. <i>Exception code 3</i>.
     */
    function __construct ( $strURL, $boolPersistent = false ) {

        $this->cstrURL = new StringHandling\URL ( $strURL );
        $this->connect();

    }

    /**
     * @access private
     */
    function __destruct () {

        switch ( $this->ServerInformation->Type ) {

            case MYSQL:

                if ( !$this->boolPersistent ) {
                    mysql_close( $this->resConnection );
                }

                break;

        }

   }

    function __get ( $strMemberName ) {

        switch ( $strMemberName ) {

            case "ServerInformation":
                return $this->_ServerInformation;

        }

    }

    private function connect () {

        switch ( $this->cstrURL->Protocol ) {

            case "mysql":

                if ( $this->boolPersistent ) {
                    $this->resConnection = @mysql_pconnect( $this->cstrURL->Host, $this->cstrURL->User, $this->cstrURL->Password );
                } else {
                    $this->resConnection = @mysql_connect( $this->cstrURL->Host, $this->cstrURL->User, $this->cstrURL->Password );
                }

                if ( !$this->resConnection ) {
                    throw new \Exception ( "Cannot connect to the MySQL database server. mysql_error() says: " . mysql_error(), 1 );
                }

                break;


            case "sqlite":

                if ( $this->boolPersistent ) {
                    $this->resConnection = @sqlite_popen( $this->ServerInformation->Filename, 0666, $strSQLiteError );
                } else {
                    $this->resConnection = @sqlite_open( $this->ServerInformation->Filename, 0666, $strSQLiteError );
                }

                if ( !$this->resConnection )
                    throw new \Exception ( "Cannot open the SQLite database. \$error_message says: " . $strSQLiteError, 3 );

                break;

        }

   }

    /**
     * Runs one query and returns a single record.
     *
     * @param string $strQuery
     * @return array|boolean An array containing the fields of the record on
     * success, <kbd>false</kbd> if no record was found.
     * @exception There is an error in the query. <i>Exception code 4</i>.
     */
    public function query ( $strQuery ) {

        /**
         * @todo Ensure that <var>$strQuery</var> is a single query and throw an
         * exception it's not.
         */

        switch ( $this->ServerInformation->Type ) {

            case MYSQL:

                $res = @mysql_query( $strQuery, $this->resConnection );
                $this->count();

                if ( $res and $res !== true ) { # Queries that returns a resource.
                    return ( mysql_num_rows( $res ) == 0 ) ? false : mysql_fetch_assoc( $res ); # Returns false if there are no rows were found.
                } elseif ( $res === true ) { # Queries that doesn't return anything.
                    // TODO:  Return number of affected rows.
                    return true;
                } else { # There was an error in the query.
                    throw new \Exception ( "There is an error in the query. mysql_error() says: '" . mysql_error( $this->resConnection ) . "', about: '" . $strQuery . "'", 4 );
                }

                break;


            case SQLITE:

                $res = @sqlite_query( $this->resConnection, $strQuery, SQLITE_ASSOC, $strSQLiteError );
                $this->count();

                if ( $res ) {
                    # TODO: Do something with this resource.
                } else {
                    throw new \Exception ( "There was an error in your SQL-query. ( " . $strQuery . " ) ( " . $strSQLiteError . " )" );
                }

                break;

        }

    }

    /**
     * Runs multiple queries, but won't return anything.
     *
     * @param array|string $mixQuery One or more queries as an array or as a string separated by semicolon.
     * @exception There is an error in the query. <i>Exception code 4</i>.
     * @todo Support for MySQL.
     */
    public function exec ( $mixQuery ) {

        switch ( $this->ServerInformation->Type ) {

            case MYSQL:
                break;

            case SQLITE:

                if ( !@sqlite_exec( $this->resConnection, $strQuery, $strSQLiteError ) )
                    throw new \Exception ( "There was an error in your SQL-query. ) " . $strQuery . " ) ( " . $strSQLiteError . " )" );

                break;

        }

    }

    /**
     * Runs one query to extract one or more records.
     *
     * @param string $strQuery
     * @return array A multidimensional array of records.
     * @exception No database is set. <i>Exception code x</i>.
     * @exception There is an error in the query. <i>Exception code 4</i>.
     * @todo Support for SQLite.
     * @todo Check that this actually is a query that can return multiple rows, and throw an exception if not.
     */
    public function fetch ( $strQuery ) {

        // This throws an exception if no database is set.
        $this->issetDatabase();


        $arrData = array();

        switch ( $this->ServerInformation->Type ) {

            case MYSQL:

                $res = @mysql_query( $strQuery, $this->resConnection );
                $this->count();

                if ( $res ) {
                    if ( mysql_num_rows( $res ) === 0 ) {
                        return false;
                    } else {
                        while ( $arrRow = mysql_fetch_assoc( $res ) ) {
                            $arrData[] = $arrRow;
                        }
                        return $arrData;
                    }
                } else {
                    throw new \Exception ( "There was an error in your SQL-query. )" . $strQuery . ") ( " . mysql_error( $this->resConnection ) . " )" );
                }

                break;

        }

    }

    /**
     * Gets the key from the last <kbd>INSERT</kbd> operation.
     *
     * @return boolean|integer The key from the last <kbd>INSERT</kbd> operation as an integer, <kbd>false</kbd> if the last operation was not an <kbd>INSERT</kbd> operation.
     */
    public function getInsertKey () {

        // Note: This function behaves slightly different for MySQL and SQLite.
        // Q: What's different?

        switch ( $this->ServerInformation->Type ) {

            case MYSQL:
                return ( mysql_insert_id( $this->resConnection ) === 0 ) ? false : mysql_insert_id( $this->resConnection ); # CAUTION: This doesn't work if the column holding the key is set to bigint (see PHP manual on mysql_insert_id()).
                break;

            case SQLITE:
                return sqlite_last_insert_rowid( $this->resConnection );
                break;

        }

    }

    /**
     * Sets the database to use.
     *
     * @param string $strDatabase
     * @exception Cannot select the database on the MySQL database server. <i>Exception code 1</i>.
     */
    public function setDatabase ( $strDatabase ) {

        $this->strDatabase = $strDatabase;

        switch ( $this->ServerInformation->Type ) {

            case MYSQL:
                if ( !mysql_select_db( $strDatabase, $this->resConnection ) ) {
                    throw new \Exception ( "Cannot select the database on the MySQL database server. mysql_error() says: " . mysql_error( $this->resConnection ), 2 );
                }
                break;

        }

    }

    /**
     * Gets the currently used database.
     *
     * @return string
     */
    public function getCurrentDatabase () {
        return $this->strDatabase;
    }

    /**
     * Escapes special characters for use in a query.
     * @param string $str
     * @return string
     */
    public function escape ( $str ) {

        switch ( $this->ServerInformation->Type ) {

            case MYSQL:
                return mysql_real_escape_string( $str, $this->resConnection );

            case SQLITE:
                return sqlite_escape_string( $str );

        }

    }

    /**
     * Throws an exception if no database is set.
     *
     * @exception No database is set. <i>Exception code x</i>.
     */
    protected function issetDatabase() {

        if ( !isset( $this->strDatabase ) ) {
            throw new \Exception ( "No database is set." );
        }

    }

    /**
     * Increments the number of queries sent by one.
     */
    protected function count () {
        $this->intNumQueriesSent++;
        self::$intTotalNumQueriesSent++;
    }

    /**
     * Gets the number of queries sent.
     *
     * @return integer
     */
    public function getNumQueriesSent () {
        return $this->intNumQueriesSent;
    }

    /**
     * Gets the total number of queries sent aross all instances of Connection.
     *
     * @return integer
     */
    public static function getTotalNumQueriesSent () {
        return self::$intTotalNumQueriesSent;
    }


}


?>
